/******************************************************************
 * Copyright (c) 2024 Bocloud Technologies Co., Ltd.
 * installer is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain n copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 ******************************************************************/

package controllers

import (
	"context"
	"fmt"
	"sync"
	"time"

	"go.uber.org/zap"
	agentv1beta1 "gopkg.openfuyao.cn/bkeagent/api/v1beta1"
	confv1beta1 "gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	bkenode "gopkg.openfuyao.cn/bkecommon/cluster/node"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	kerrors "k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/client-go/tools/record"
	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
	"sigs.k8s.io/cluster-api/util"
	"sigs.k8s.io/cluster-api/util/annotations"
	"sigs.k8s.io/cluster-api/util/patch"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/builder"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	"sigs.k8s.io/controller-runtime/pkg/handler"

	bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/command"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/mergecluster"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/phaseframe/phaseutil"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/annotation"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/clusterutil"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/condition"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/constant"
	labelhelper "gopkg.openfuyao.cn/cluster-api-provider-bke/utils/label"
	l "gopkg.openfuyao.cn/cluster-api-provider-bke/utils/log"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/predicates"
)

// BKEMachineReconciler reconciles a BKEMachine object
type BKEMachineReconciler struct {
	client.Client
	Scheme   *runtime.Scheme
	Recorder record.EventRecorder

	nodesBootRecord map[string]struct{}
	mux             sync.Mutex
}

const machineControllerName = "bke-machine-controller"

// +kubebuilder:rbac:groups=bke.bocloud.com,resources=*,verbs=get;list;watch;create;update;patch;delete
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the BKEMachine object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile
func (r *BKEMachineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, rerr error) {
	log := l.With("controller", machineControllerName)

	// 1 Fetch the BKEMachine instance.
	bkeMachine := &bkev1beta1.BKEMachine{}
	if err := r.Client.Get(ctx, req.NamespacedName, bkeMachine); err != nil {
		if apierrors.IsNotFound(err) {
			return ctrl.Result{}, nil
		}
		return ctrl.Result{}, err
	}

	log = log.With("bkeMachine", bkeMachine.Name)

	// Fetch the Machine.
	machine, err := util.GetOwnerMachine(ctx, r.Client, bkeMachine.ObjectMeta)
	if machine == nil {
		log.Info("Waiting for Machine Controller to set OwnerRef on BKEMachine")
		return ctrl.Result{}, nil
	}
	if err != nil {
		return ctrl.Result{}, err
	}

	log = log.With("machine", machine.Name)
	// Fetch the Cluster.
	cluster, err := util.GetClusterFromMetadata(ctx, r.Client, machine.ObjectMeta)
	if err != nil {
		log.Info("BKEMachine owner Machine is missing cluster label or cluster does not exist")
		return ctrl.Result{}, err
	}
	if cluster == nil {
		log.Info(fmt.Sprintf("Please associate this machine with a cluster using the label %s: <name of cluster>", clusterv1.ClusterNameLabel))
		return ctrl.Result{}, nil
	}

	log = log.With("cluster", cluster.Name)

	// Return early if the object or Cluster is paused.
	if annotations.IsPaused(cluster, bkeMachine) {
		log.Info("Reconciliation is paused for this object")
		return ctrl.Result{}, nil
	}

	// Fetch the BKE Cluster.
	bkeCluster, err := mergecluster.GetCombinedBKECluster(ctx, r.Client, bkeMachine.Namespace, cluster.Spec.InfrastructureRef.Name)
	if err != nil {
		return ctrl.Result{}, nil
	}

	log = log.With("bkeCluster", bkeCluster.Name)

	// Initialize the patch helper
	patchHelper, err := patch.NewHelper(bkeMachine, r.Client)
	if err != nil {
		return ctrl.Result{}, err
	}
	// Always attempt to Patch the bkeMachine object and Status after each reconciliation.
	defer func() {
		if !controllerutil.ContainsFinalizer(bkeMachine, bkev1beta1.BKEMachineFinalizer) {
			return
		}
		if err := patchBKEMachine(ctx, patchHelper, bkeMachine); err != nil {
			if apierrors.IsNotFound(err) {
				return
			}
			log.Errorf("failed to patch bkeMachine")
			if rerr == nil {
				rerr = err
			}
		}
	}()

	// Add finalizer first if not exist to avoid the race condition between init and delete
	if !controllerutil.ContainsFinalizer(bkeMachine, bkev1beta1.BKEMachineFinalizer) {
		controllerutil.AddFinalizer(bkeMachine, bkev1beta1.BKEMachineFinalizer)
		return ctrl.Result{}, nil
	}

	// Handle deleted bke machines
	if !bkeMachine.ObjectMeta.DeletionTimestamp.IsZero() {
		return r.reconcileDelete(ctx, machine, cluster, bkeMachine, bkeCluster, log)
	}

	// Check if the infrastructure is ready, otherwise return and wait for the cluster object to be updated
	if !cluster.Status.InfrastructureReady {
		log.Info("Waiting for BKECluster Controller to create cluster infrastructure")
		return ctrl.Result{}, nil
	}

	//查询 bkemachine 关联的 node，如果关联的节点目前状态被标记了失败状态码，则直接返回
	hostIp, found := labelhelper.CheckBKEMachineLabel(bkeMachine)
	if found && bkeCluster.GetNodeStateFlag(hostIp, bkev1beta1.NodeFailedFlag) {
		return ctrl.Result{}, nil
	}

	return r.reconcile(ctx, machine, cluster, bkeMachine, bkeCluster, log)
}

func (r *BKEMachineReconciler) reconcile(ctx context.Context, machine *clusterv1.Machine, cluster *clusterv1.Cluster, bkeMachine *bkev1beta1.BKEMachine, bkeCluster *bkev1beta1.BKECluster, log *zap.SugaredLogger) (ctrl.Result, error) {
	if bkeCluster.Status.ClusterStatus == bkev1beta1.ClusterDeleting {
		log.Info("bkeCluster is in deleting phase, waiting for bkeCluster to be deleted")
		return ctrl.Result{}, nil
	}

	phases := []func(ctx context.Context, machine *clusterv1.Machine, cluster *clusterv1.Cluster, bkeMachine *bkev1beta1.BKEMachine, bkeCluster *bkev1beta1.BKECluster, log *zap.SugaredLogger) (ctrl.Result, error){
		r.reconcileCommand,
		r.reconcileBootstrap,
	}

	res := ctrl.Result{}
	var errs []error
	for _, phase := range phases {
		// Call the inner reconciliation methods.
		phaseResult, err := phase(ctx, machine, cluster, bkeMachine, bkeCluster, log)
		if err != nil {
			errs = append(errs, err)
		}
		if len(errs) > 0 {
			continue
		}
		res = util.LowestNonZeroResult(res, phaseResult)
	}
	return res, kerrors.NewAggregate(errs)
}

// reconcileDelete handles BKEMachine deletion.
func (r *BKEMachineReconciler) reconcileDelete(ctx context.Context, machine *clusterv1.Machine, cluster *clusterv1.Cluster, bkeMachine *bkev1beta1.BKEMachine, bkeCluster *bkev1beta1.BKECluster, log *zap.SugaredLogger) (ctrl.Result, error) {
	log = log.Named("reconcileBKEMachineDelete").With("bkeMachine", bkeMachine.Name)

	defer func() {
		// todo 和下面的一段defer类似，但需要优化下看保留哪个
		if !controllerutil.ContainsFinalizer(bkeMachine, bkev1beta1.BKEMachineFinalizer) {
			nodeIP, found := labelhelper.CheckBKEMachineLabel(bkeMachine)
			if found && nodeIP != "" {
				r.mux.Lock()
				delete(r.nodesBootRecord, nodeIP)
				r.mux.Unlock()

				// remove node from BKECluster.Status.NodesStatus
				bkeCluster.RemoveNodeState(nodeIP)
				// remove node from AppointmentDeletedNodesAnnotationKey
				patchFunc := func(cluster *bkev1beta1.BKECluster) {
					phaseutil.RemoveAppointmentDeletedNodes(cluster, nodeIP)
				}
				_ = mergecluster.SyncStatusUntilComplete(r.Client, bkeCluster, patchFunc)
			}
		}
	}()

	// return early if the bkeMachine is already marked for deletion.
	if isMarkDeletion(bkeMachine) {
		if !controllerutil.ContainsFinalizer(bkeMachine, bkev1beta1.BKEMachineFinalizer) {
			return ctrl.Result{}, nil
		}
		log.Debug("BKEMachine is already marked for deletion")
		return r.reconcileCommand(ctx, machine, cluster, bkeMachine, bkeCluster, log)
	}

	patchHelper, err := patch.NewHelper(bkeMachine, r.Client)
	if err != nil {
		log.Warnf("failed to create patch helper for bkeMachine %s, requeue", bkeMachine.Name)
		return ctrl.Result{Requeue: true}, nil
	}
	defer patchBKEMachine(ctx, patchHelper, bkeMachine)

	// Mark the bkeMachine as deleted to avoid re-entry
	if err := markBKEMachineDeletion(ctx, bkeMachine, patchHelper); err != nil {
		r.logWarningAndEvent(log, bkeCluster, constant.CommandCreateFailedReason, "failed to mark BKEMachine %s as deleted, retry after 10 second", bkeMachine.Name)
		return ctrl.Result{RequeueAfter: 10 * time.Second}, err
	}

	bkeNodes := phaseutil.GetBKENodesFromNodesStatus(bkeCluster.Status.NodesStatus)
	log.Debug("step 1 get bke node from bke cluster Status")
	node, err := bkeMachineToNode(bkeMachine, bkeNodes)
	if err != nil {
		log.Warnf("failed to get node from bkeCluster Status, force delete: %v", err)
		controllerutil.RemoveFinalizer(bkeMachine, bkev1beta1.BKEMachineFinalizer)
		return ctrl.Result{}, nil
	}

	bkeCluster.SetNodeStateWithMessage(node.IP, bkev1beta1.NodeDeleting, "Deleting")
	if err := mergecluster.SyncStatusUntilComplete(r.Client, bkeCluster); err != nil {
		return ctrl.Result{}, err
	}

	defer func() {
		if !controllerutil.ContainsFinalizer(bkeMachine, bkev1beta1.BKEMachineFinalizer) {
			r.mux.Lock()
			delete(r.nodesBootRecord, node.IP)
			r.mux.Unlock()

			// remove node from BKECluster.Status.NodesStatus
			bkeCluster.RemoveNodeState(node.IP)
			// remove node from AppointmentDeletedNodesAnnotationKey
			patchFunc := func(cluster *bkev1beta1.BKECluster) {
				phaseutil.RemoveAppointmentDeletedNodes(cluster, node.IP)
			}
			_ = mergecluster.SyncStatusUntilComplete(r.Client, bkeCluster, patchFunc)
		}
	}()

	log = log.With("node", phaseutil.NodeInfo(*node))

	if condition.HasConditionStatus(bkev1beta1.SwitchBKEAgentCondition, bkeCluster, confv1beta1.ConditionTrue) {
		log.Info("agent not listening current cluster, delete BKEMachine directly")
		controllerutil.RemoveFinalizer(bkeMachine, bkev1beta1.BKEMachineFinalizer)
		return ctrl.Result{}, nil
	}

	// 不删除目标集群，如果有注解，且注解值为true或没有注释时
	if bkeCluster.Status.ClusterStatus == bkev1beta1.ClusterDeleting {
		if v, ok := annotation.HasAnnotation(bkeCluster, annotation.DeleteIgnoreTargetClusterAnnotationKey); (ok && v == "true") || !ok {
			log.Info("ingore delete target cluster, delete BKEMachine directly")
			controllerutil.RemoveFinalizer(bkeMachine, bkev1beta1.BKEMachineFinalizer)
			return ctrl.Result{}, nil
		}
	}

	log.Debug("step 3 check BKEAgent is running")
	// 没有推送过agent，直接删除不做清理了
	if !bkeCluster.GetNodeStateFlag(node.IP, bkev1beta1.NodeAgentReadyFlag) {
		controllerutil.RemoveFinalizer(bkeMachine, bkev1beta1.BKEMachineFinalizer)
		return ctrl.Result{}, nil
	}

	log = log.With("node", phaseutil.NodeInfo(*node))
	log.Debugf("step 4 create reset command for node %s", phaseutil.NodeInfo(*node))

	// extra is used to store extra ip in node interface、file or directory to remove
	var extra []string
	if clusterutil.AvailableLoadBalancerEndPoint(bkeCluster.Spec.ControlPlaneEndpoint, bkeCluster.Spec.ClusterConfig.Nodes) {
		extra = append(extra, bkeCluster.Spec.ControlPlaneEndpoint.Host)
	}
	ingressVip, _ := clusterutil.GetIngressConfig(bkeCluster.Spec.ClusterConfig.Addons)
	if ingressVip != "" && ingressVip != bkeCluster.Spec.ControlPlaneEndpoint.Host {
		extra = append(extra, ingressVip)
	}

	v, ok := annotation.HasAnnotation(bkeCluster, annotation.DeepRestoreNodeAnnotationKey)
	deepRestore := (ok && v == "true") || !ok
	reset := command.Reset{
		BaseCommand: command.BaseCommand{
			Ctx:             ctx,
			NameSpace:       bkeCluster.Namespace,
			Client:          r.Client,
			Scheme:          r.Scheme,
			OwnerObj:        bkeMachine,
			ClusterName:     bkeCluster.Name,
			Unique:          true,
			RemoveAfterWait: true,
		},
		Node:        node,
		BKEConfig:   bkeCluster.Name,
		Extra:       extra,
		DeepRestore: deepRestore,
	}
	if err := reset.New(); err != nil {
		if apierrors.HasStatusCause(err, corev1.NamespaceTerminatingCause) {
			controllerutil.RemoveFinalizer(bkeMachine, bkev1beta1.BKEMachineFinalizer)
			return ctrl.Result{}, nil
		}

		errInfo := "failed to create reset command"
		r.logErrorAndEvent(log, bkeCluster, constant.CommandCreateFailedReason, "%s: %v", errInfo, err)
		return ctrl.Result{}, nil
	}

	r.logInfoAndEvent(log, bkeCluster, constant.CommandCreateSuccessReason, "reset command created, node  %q", phaseutil.NodeInfo(*node))
	// wait for reset command to be completed
	log.Debug("step 5 wait for reset command to be completed")
	err, _, failed := reset.Wait()
	if err != nil || len(failed) > 0 {
		log.Infof("failed to wait for reset command to be completed, delete directly, err: %v", err)
		// remove finalizer
		controllerutil.RemoveFinalizer(bkeMachine, bkev1beta1.BKEMachineFinalizer)
		return ctrl.Result{}, nil
	}
	r.logInfoAndEvent(log, bkeCluster, constant.WorkerDeletedReason, "reset command completed, remove node %q from cluster %q", node.IP, cluster.Name)

	// 关闭agent
	shutDowmAgentCommand := command.Custom{
		BaseCommand: command.BaseCommand{
			Ctx:             ctx,
			NameSpace:       bkeCluster.Namespace,
			Client:          r.Client,
			Scheme:          r.Scheme,
			OwnerObj:        bkeCluster,
			ClusterName:     bkeCluster.Name,
			Unique:          true,
			RemoveAfterWait: true,
			WaitTimeout:     10 * time.Second,
		},
		Nodes:        bkenode.Nodes{*node},
		CommandName:  fmt.Sprintf("shutdown-agent-%s", node.IP),
		CommandLabel: command.BKEClusterLabel,
	}
	commandSpec := command.GenerateDefaultCommandSpec()
	commandSpec.Commands = []agentv1beta1.ExecCommand{
		{
			ID: "Shutdown agent",
			Command: []string{
				"Shutdown",
			},
			Type:          agentv1beta1.CommandBuiltIn,
			BackoffIgnore: false,
		},
	}
	shutDowmAgentCommand.CommandSpec = commandSpec
	_ = shutDowmAgentCommand.New()
	_, _, _ = shutDowmAgentCommand.Wait()

	controllerutil.RemoveFinalizer(bkeMachine, bkev1beta1.BKEMachineFinalizer)
	return ctrl.Result{}, nil
}

// patchBKEMachine will patch the BKEMachine
func patchBKEMachine(ctx context.Context, patchHelper *patch.Helper, bkeMachine *bkev1beta1.BKEMachine) error {
	return patchHelper.Patch(ctx, bkeMachine)
}

// SetupWithManager sets up the controller with the Manager.
func (r *BKEMachineReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error {
	r.nodesBootRecord = make(map[string]struct{})
	r.mux = sync.Mutex{}

	clusterToBKEMachines, err := util.ClusterToObjectsMapper(mgr.GetClient(), &bkev1beta1.BKEMachineList{}, mgr.GetScheme())
	if err != nil {
		return err
	}

	return ctrl.NewControllerManagedBy(mgr).
		For(&bkev1beta1.BKEMachine{}).
		WithOptions(options).
		Watches(
			&agentv1beta1.Command{},
			handler.EnqueueRequestForOwner(mgr.GetScheme(), mgr.GetRESTMapper(), &bkev1beta1.BKEMachine{}, handler.OnlyControllerOwner()),
			builder.WithPredicates(predicates.CommandUpdateCompleted()),
		).
		Watches(
			&clusterv1.Machine{},
			handler.EnqueueRequestsFromMapFunc(util.MachineToInfrastructureMapFunc(bkev1beta1.GroupVersion.WithKind("BKEMachine"))),
		).
		Watches(
			&clusterv1.Cluster{},
			handler.EnqueueRequestsFromMapFunc(clusterToBKEMachines),
			builder.WithPredicates(predicates.ClusterUnPause()),
		).
		Watches(
			&bkev1beta1.BKECluster{},
			handler.EnqueueRequestsFromMapFunc(r.BKEClusterToBKEMachines),
			builder.WithPredicates(predicates.BKEAgentReady(), predicates.BKEClusterUnPause()),
		).
		Complete(r)
}

// BKEClusterToBKEMachines is a handler.ToRequestsFunc to be used to enqeue
// requests for reconciliation of BKEMachines.
func (r *BKEMachineReconciler) BKEClusterToBKEMachines(ctx context.Context, o client.Object) []ctrl.Request {
	result := []ctrl.Request{}
	c, ok := o.(*bkev1beta1.BKECluster)
	if !ok {
		panic(fmt.Sprintf("Expected a BKECluster but got a %T", o))
	}

	cluster, err := util.GetOwnerCluster(context.TODO(), r.Client, c.ObjectMeta)
	switch {
	case apierrors.IsNotFound(err) || cluster == nil:
		return result
	case err != nil:
		return result
	}

	labels := map[string]string{clusterv1.ClusterNameLabel: cluster.Name}
	machineList := &clusterv1.MachineList{}
	if err := r.Client.List(context.TODO(), machineList, client.InNamespace(c.Namespace), client.MatchingLabels(labels)); err != nil {
		return nil
	}
	for _, m := range machineList.Items {
		if m.Spec.InfrastructureRef.Name == "" || m.Status.BootstrapReady {
			continue
		}
		name := client.ObjectKey{Namespace: m.Spec.InfrastructureRef.Namespace, Name: m.Spec.InfrastructureRef.Name}
		result = append(result, ctrl.Request{NamespacedName: name})
	}

	return result
}

// LogCommandFailed log command failed message
func (r *BKEMachineReconciler) LogCommandFailed(cmd agentv1beta1.Command, bkeCluster *bkev1beta1.BKECluster, failedNods []string, log *zap.SugaredLogger, reson string) string {
	for _, node := range failedNods {
		for _, condition := range cmd.Status[node].Conditions {
			if condition.Status == metav1.ConditionFalse && (condition.StdErr != nil || len(condition.StdErr) > 0) {
				// 输出最后一次运行的错误信息
				r.logWarningAndEvent(log, bkeCluster, reson, "Node %q, Command %s, sub ID %q, err: %s", node, utils.ClientObjNS(&cmd), condition.ID, condition.StdErr[len(condition.StdErr)-1])
				return condition.StdErr[len(condition.StdErr)-1]
			}
		}
	}
	return ""
}

func (r *BKEMachineReconciler) logInfoAndEvent(log *zap.SugaredLogger, bkeCluster *bkev1beta1.BKECluster, reason, msg string, args ...interface{}) {
	tameStamp := time.Now().Unix()
	msg = fmt.Sprintf("(%d) %s", tameStamp, msg)
	r.Recorder.AnnotatedEventf(bkeCluster, annotation.BKENormalEventAnnotation(), corev1.EventTypeNormal, reason, msg, args...)
	if log != nil {
		log.Infof(msg, args...)
		return
	}
	l.Infof(msg, args...)
}

func (r *BKEMachineReconciler) logErrorAndEvent(log *zap.SugaredLogger, bkeCluster *bkev1beta1.BKECluster, reason, msg string, args ...interface{}) {
	tameStamp := time.Now().Unix()
	msg = fmt.Sprintf("(%d) %s", tameStamp, msg)
	r.Recorder.AnnotatedEventf(bkeCluster, annotation.BKENormalEventAnnotation(), corev1.EventTypeWarning, reason, msg, args...)
	if log != nil {
		log.Errorf(msg, args...)
		return
	}
	l.Errorf(msg, args...)
}

func (r *BKEMachineReconciler) logWarningAndEvent(log *zap.SugaredLogger, bkeCluster *bkev1beta1.BKECluster, reason, msg string, args ...interface{}) {
	tameStamp := time.Now().Unix()
	msg = fmt.Sprintf("(%d) %s", tameStamp, msg)
	r.Recorder.AnnotatedEventf(bkeCluster, annotation.BKENormalEventAnnotation(), corev1.EventTypeWarning, reason, msg, args...)
	if log != nil {
		log.Warnf(msg, args...)
		return
	}
	l.Warnf(msg, args...)
}

func (r *BKEMachineReconciler) logFinishAndEvent(log *zap.SugaredLogger, bkeCluster *bkev1beta1.BKECluster, reason, msg string, args ...interface{}) {
	tameStamp := time.Now().Unix()
	msg = fmt.Sprintf("(%d) %s", tameStamp, msg)
	r.Recorder.AnnotatedEventf(bkeCluster, annotation.BKEFinishEventAnnotation(), corev1.EventTypeNormal, reason, msg, args...)
	if log != nil {
		log.Infof(msg, args...)
		return
	}
	l.Infof(msg, args...)
}

// bkeMachineToNode convert bke machine to node
func bkeMachineToNode(bkeMachine *bkev1beta1.BKEMachine, bkeNodes bkenode.Nodes) (*confv1beta1.Node, error) {
	if bkeMachine.Status.Node != nil {
		return bkeMachine.Status.Node, nil
	}

	hostIP, ok := labelhelper.CheckBKEMachineLabel(bkeMachine)
	if !ok {
		return nil, fmt.Errorf("bke machine %s label is not found", bkeMachine.Name)
	}
	nodes := bkeNodes.Filter(bkenode.FilterOptions{"IP": hostIP})
	if nodes.Length() == 0 {
		l.Warnf("node %s is not found in BKECluster.Status.Nodes, maybe already deleted", hostIP)
		// still try to create node
		return &confv1beta1.Node{
			IP: hostIP,
		}, nil
	}
	return &nodes[0], nil
}

// markBKEMachineDeletion mark bke machine deletion
func markBKEMachineDeletion(ctx context.Context, bkeMachine *bkev1beta1.BKEMachine, patchHelper *patch.Helper) error {
	as := bkeMachine.GetAnnotations()
	if as == nil {
		as = map[string]string{}
	}
	as[clusterv1.DeleteMachineAnnotation] = ""
	bkeMachine.SetAnnotations(as)
	return patchHelper.Patch(ctx, bkeMachine)
}

// isMarkDeletion check if bke machine is marked deletion
func isMarkDeletion(bkeMachine *bkev1beta1.BKEMachine) bool {
	as := bkeMachine.GetAnnotations()
	if as == nil {
		return false
	}
	_, ok := as[clusterv1.DeleteMachineAnnotation]
	return ok
}

// getBKEMachineAssociateCommands get bke machine associate commands
func getBKEMachineAssociateCommands(ctx context.Context, c client.Client, bkeCluster *bkev1beta1.BKECluster, bkeMachine *bkev1beta1.BKEMachine) ([]agentv1beta1.Command, error) {
	commandsLi := agentv1beta1.CommandList{}
	filters := phaseutil.GetListFiltersByBKECluster(bkeCluster)

	if err := c.List(ctx, &commandsLi, filters...); err != nil {
		return nil, err
	}
	commands := []agentv1beta1.Command{}
	for _, cmd := range commandsLi.Items {
		if !command.IsOwnerRefCommand(bkeMachine, cmd) {
			continue
		}
		if _, ok := cmd.Annotations[annotation.CommandReconciledAnnotationKey]; ok {
			continue
		}
		if err := command.ValidateCommand(&cmd); err != nil {
			l.Error(cmd.Name, err)
			continue
		}
		commands = append(commands, cmd)
	}

	return commands, nil
}
